﻿/* 项目“Jy.Abp.Ddd.Application (netstandard2.0)”的未合并的更改
在此之前:
using System;
在此之后:
using Jy;
using Jy.Abp;
using Jy.Abp;
using Jy.Abp.Tests;
using System;
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;

namespace Jy.Abp.Application;

public class AppCrudAppService<TEntity, TKey, TEntityDto, TEntityListItem, TGetListFilter, TCreateInput, TUpdateInput>
    : AppService<TEntity, TKey>
       where TEntity : class, IEntity<TKey>
       where TEntityDto : IEntityDto<TKey>
       where TEntityListItem : IEntityDto<TKey>
       where TGetListFilter : PagedAndSortedFilter
    //where TUpdateInput : IEntityDto<TKey>
{
    protected virtual string CreatePolicyName { get; set; }
    protected virtual string GetPolicyName { get; set; }
    protected virtual string GetListPolicyName { get; set; }
    protected virtual string UpdatePolicyName { get; set; }
    protected virtual string DeletePolicyName { get; set; }

    public AppCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
    {

    }

    #region 权限
    protected virtual async Task CheckCreatePolicyAsync()
    {
        await CheckPolicyAsync(CreatePolicyName);
    }

    protected virtual async Task CheckGetPolicyAsync()
    {
        await CheckPolicyAsync(GetPolicyName);
    }

    protected virtual async Task CheckGetListPolicyAsync()
    {
        await CheckPolicyAsync(GetListPolicyName);
    }

    protected virtual async Task CheckUpdatePolicyAsync()
    {
        await CheckPolicyAsync(UpdatePolicyName);
    }

    protected virtual async Task CheckDeletePolicyAsync()
    {
        await CheckPolicyAsync(DeletePolicyName);
    }
    #endregion

    #region 对像映射
    /// <summary>
    /// Maps <typeparamref name="TCreateInput"/> to <typeparamref name="TEntity"/> to create a new entity.
    /// It uses <see cref="MapToEntity(TCreateInput)"/> by default.
    /// It can be overriden for custom mapping.
    /// Overriding this has higher priority than overriding the <see cref="MapToEntity(TCreateInput)"/>
    /// </summary>
    protected virtual Task<TEntity> MapToEntityAsync(TCreateInput createInput)
    {
        return Task.FromResult(MapToEntity(createInput));
    }

    /// <summary>
    /// Maps <typeparamref name="TCreateInput"/> to <typeparamref name="TEntity"/> to create a new entity.
    /// It uses <see cref="IObjectMapper"/> by default.
    /// It can be overriden for custom mapping.
    /// </summary>
    protected virtual TEntity MapToEntity(TCreateInput createInput)
    {
        var entity = ObjectMapper.Map<TCreateInput, TEntity>(createInput);
        SetIdForGuids(entity);
        return entity;
    }

    /// <summary>
    /// Maps <typeparamref name="TUpdateInput"/> to <typeparamref name="TEntity"/> to update the entity.
    /// It uses <see cref="MapToEntity(TKey,TUpdateInput, TEntity)"/> by default.
    /// It can be overriden for custom mapping.
    /// Overriding this has higher priority than overriding the <see cref="MapToEntity(TKey,TUpdateInput, TEntity)"/>
    /// </summary>
    protected virtual Task MapToEntityAsync(TKey id, TUpdateInput updateInput, TEntity entity)
    {
        MapToEntity(id, updateInput, entity);
        return Task.CompletedTask;
    }

    /// <summary>
    /// Maps <typeparamref name="TUpdateInput"/> to <typeparamref name="TEntity"/> to update the entity.
    /// It uses <see cref="IObjectMapper"/> by default.
    /// It can be overriden for custom mapping.
    /// </summary>
    protected virtual void MapToEntity(TKey id, TUpdateInput updateInput, TEntity entity)
    {
        ObjectMapper.Map(updateInput, entity);
    }

    /// <summary>
    /// Maps <typeparamref name="TEntity"/> to <typeparamref name="TEntityDto"/>.
    /// It internally calls the <see cref="MapToGetOutputDto"/> by default.
    /// It can be overriden for custom mapping.
    /// Overriding this has higher priority than overriding the <see cref="MapToGetOutputDto"/>
    /// </summary>
    protected virtual Task<TEntityDto> MapToGetOutputDtoAsync(TEntity entity)
    {
        return Task.FromResult(MapToGetOutputDto(entity));
    }

    /// <summary>
    /// Maps <typeparamref name="TEntity"/> to <typeparamref name="TEntityDto"/>.
    /// It uses <see cref="IObjectMapper"/> by default.
    /// It can be overriden for custom mapping.
    /// </summary>
    protected virtual TEntityDto MapToGetOutputDto(TEntity entity)
    {
        return ObjectMapper.Map<TEntity, TEntityDto>(entity);
    }

    /// <summary>
    /// Maps a list of <typeparamref name="TEntity"/> to <typeparamref name="TEntityListItem"/> objects.
    /// It uses <see cref="MapToGetListOutputDtoAsync"/> method for each item in the list.
    /// </summary>
    protected virtual async Task<List<TEntityListItem>> MapToGetListOutputDtosAsync(List<TEntity> entities)
    {
        var dtos = new List<TEntityListItem>();

        foreach (var entity in entities)
        {
            dtos.Add(await MapToGetListOutputDtoAsync(entity));
        }

        return dtos;
    }

    /// <summary>
    /// Maps <typeparamref name="TEntity"/> to <typeparamref name="TEntityListItem"/>.
    /// It internally calls the <see cref="MapToGetListOutputDto"/> by default.
    /// It can be overriden for custom mapping.
    /// Overriding this has higher priority than overriding the <see cref="MapToGetListOutputDto"/>
    /// </summary>
    protected virtual Task<TEntityListItem> MapToGetListOutputDtoAsync(TEntity entity)
    {
        return Task.FromResult(MapToGetListOutputDto(entity));
    }

    /// <summary>
    /// Maps <typeparamref name="TEntity"/> to <typeparamref name="TEntityListItem"/>.
    /// It uses <see cref="IObjectMapper"/> by default.
    /// It can be overriden for custom mapping.
    /// </summary>
    protected virtual TEntityListItem MapToGetListOutputDto(TEntity entity)
    {
        return ObjectMapper.Map<TEntity, TEntityListItem>(entity);
    }
    #endregion

    #region 查询及操作处理

    /// <summary>
    /// This method should create <see cref="IQueryable{TEntity}"/> based on given input.
    /// It should filter query if needed, but should not do sorting or paging.
    /// Sorting should be done in <see cref="ApplySorting"/> and paging should be done in <see cref="ApplyPaging"/>
    /// methods.
    /// </summary>
    /// <param name="input">The input.</param>
    protected virtual async Task<IQueryable<TEntity>> CreateFilteredQueryAsync(TGetListFilter input)
    {
        return await Repository.GetQueryableAsync();
    }

    /// <summary>
    /// Should apply sorting if needed.
    /// </summary>
    /// <param name="query">The query.</param>
    /// <param name="input">The input.</param>
    protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TGetListFilter input)
    {
        //Try to sort query if available
        if (input is ISortedResultRequest sortInput)
        {
            if (sortInput.Sorting.HasValue())
            {
                return query.OrderBy(sortInput.Sorting);
            }
        }

        //IQueryable.Task requires sorting, so we should sort if Take will be used.
        if (input is ILimitedResultRequest)
        {
            return ApplyDefaultSorting(query);
        }

        //No sorting
        return query;
    }

    /// <summary>
    /// Applies sorting if no sorting specified but a limited result requested.
    /// </summary>
    /// <param name="query">The query.</param>
    protected virtual IQueryable<TEntity> ApplyDefaultSorting(IQueryable<TEntity> query)
    {
        //如果是数字ID，则默认ID倒序
        if (typeof(TKey) == typeof(int) || typeof(TKey) == typeof(long))
            return query.OrderByDescending(e => e.Id);

        //否则，按时间倒序
        if (typeof(TEntity).IsAssignableTo<IHasCreationTime>())
            return query.OrderByDescending(e => ((IHasCreationTime)e).CreationTime);

        throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyReadOnlyAppService!");
    }

    /// <summary>
    /// Should apply paging if needed.
    /// </summary>
    /// <param name="query">The query.</param>
    /// <param name="input">The input.</param>
    protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TGetListFilter input)
    {
        if (input is PagedAndSortedFilter filter)
        {
            return query.PageBy(filter);
        }

        //Try to use paging if available
        if (input is IPagedResultRequest pagedInput)
        {
            return query.PageBy(pagedInput);
        }


        //Try to limit query result if available
        if (input is ILimitedResultRequest limitedInput)
        {
            return query.Take(limitedInput.MaxResultCount);
        }

        //No paging
        return query;
    }

    protected virtual async Task DeleteByIdAsync(TKey id)
    {
        await Repository.DeleteAsync(id);
    }

    /// <summary>
    /// Sets Id value for the entity if <typeparamref name="TKey"/> is <see cref="Guid"/>.
    /// It's used while creating a new entity.
    /// </summary>
    protected virtual void SetIdForGuids(TEntity entity)
    {
        if (entity is IEntity<Guid> entityWithGuidId && entityWithGuidId.Id == Guid.Empty)
        {
            EntityHelper.TrySetId(
                entityWithGuidId,
                () => GuidGenerator.Create(),
                true
            );
        }
    }

    protected virtual void TryToSetTenantId(TEntity entity)
    {
        if (entity is IMultiTenant && HasTenantIdProperty(entity))
        {
            var tenantId = CurrentTenant.Id;

            if (!tenantId.HasValue)
            {
                return;
            }

            var propertyInfo = entity.GetType().GetProperty(nameof(IMultiTenant.TenantId));

            if (propertyInfo == null || propertyInfo.GetSetMethod(true) == null)
            {
                return;
            }

            propertyInfo.SetValue(entity, tenantId);
        }
    }

    protected virtual bool HasTenantIdProperty(TEntity entity)
    {
        return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null;
    }
    #endregion 

    #region 添加/查询/修改/删除
    /// <summary>
    /// 创建
    /// </summary>
    public virtual async Task<TEntityDto> CreateAsync(TCreateInput input)
    {
        await CheckCreatePolicyAsync();

        var entity = await MapToEntityAsync(input);

        TryToSetTenantId(entity);

        await Repository.InsertAsync(entity, autoSave: true);

        return await MapToGetOutputDtoAsync(entity);
    }
    /// <summary>
    /// 获取
    /// </summary>
    public virtual async Task<TEntityDto> GetAsync(TKey id)
    {
        await CheckGetPolicyAsync();

        var entity = await GetEntityByIdAsync(id);

        return await MapToGetOutputDtoAsync(entity);
    }
    /// <summary>
    /// 获取列表
    /// </summary>
    public virtual async Task<PagedResultDto<TEntityListItem>> GetListAsync(TGetListFilter input)
    {
        await CheckGetListPolicyAsync();

        var query = await CreateFilteredQueryAsync(input);

        var totalCount = await AsyncExecuter.CountAsync(query);

        query = ApplySorting(query, input);
        query = ApplyPaging(query, input);

        var entities = await AsyncExecuter.ToListAsync(query);
        var entityDtos = await MapToGetListOutputDtosAsync(entities);

        return new PagedResultDto<TEntityListItem>(
            totalCount,
            entityDtos
        );
    }
    /// <summary>
    /// 修改
    /// </summary>
    public virtual async Task<TEntityDto> UpdateAsync(TKey id, TUpdateInput input)
    {
        await CheckUpdatePolicyAsync();

        var entity = await GetEntityByIdAsync(id);
        //TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise
        await MapToEntityAsync(id, input, entity);
        await Repository.UpdateAsync(entity, autoSave: true);

        return await MapToGetOutputDtoAsync(entity);
    }
    /// <summary>
    /// 删除
    /// </summary>
    public virtual async Task DeleteAsync(TKey id)
    {
        await CheckDeletePolicyAsync();

        await DeleteByIdAsync(id);
    }

    #endregion
}

public class AsyncCrudAppService<TEntity, TKey, TEntityDto, TEntityListItem, TGetListFilter, EditDto>
    : AppCrudAppService<TEntity, TKey, TEntityDto, TEntityListItem, TGetListFilter, EditDto, EditDto>
       where TEntity : class, IEntity<TKey>
       where TEntityDto : IEntityDto<TKey>
       where TEntityListItem : IEntityDto<TKey>
       where EditDto : IEntityDto<TKey>
       where TGetListFilter : PagedAndSortedFilter
{
    public AsyncCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
    {
    }
}

public class AsyncGuidEntityCrudAppService<TEntity, TEntityDto, TEntityListItem, TGetListFilter, EditDto>
   : AppCrudAppService<TEntity, Guid, TEntityDto, TEntityListItem, TGetListFilter, EditDto, EditDto>
      where TEntity : class, IEntity<Guid>
      where TEntityDto : IEntityDto<Guid>
      where TEntityListItem : IEntityDto<Guid>
      where EditDto : IEntityDto<Guid>
      where TGetListFilter : PagedAndSortedFilter
{
    public AsyncGuidEntityCrudAppService(IRepository<TEntity, Guid> repository) : base(repository)
    {
    }
}

public class AsyncIntEntityCrudAppService<TEntity, TEntityDto, TEntityListItem, TGetListFilter, EditDto>
   : AppCrudAppService<TEntity, int, TEntityDto, TEntityListItem, TGetListFilter, EditDto, EditDto>
      where TEntity : class, IEntity<int>
      where TEntityDto : IEntityDto<int>
      where TEntityListItem : IEntityDto<int>
      where EditDto : IEntityDto<int>
      where TGetListFilter : PagedAndSortedFilter
{
    public AsyncIntEntityCrudAppService(IRepository<TEntity, int> repository) : base(repository)
    {
    }
}